/*-------------------------------------------------------------------------
 *
 * startup.c
 *
 * 启动进程初始化服务器并执行任何已指定的恢复操作。请注意，没有“主循环”，因为一旦初始化完成，启动进程就结束了。（在待机模式下，可以将重放循环视为主循环。）
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/postmaster/startup.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <unistd.h>

#include "access/xlog.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/interrupt.h"
#include "postmaster/startup.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/standby.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/timeout.h"


#ifndef USE_POSTMASTER_DEATH_SIGNAL
/*
 * 在需要进行系统调用以查明主进程是否已经消失的系统上，
 * 我们会仅在每第 N 次调用 HandleStartupProcInterrupts() 时进行。
 * 这只会影响我们在忙于重放 WAL 时检测该条件所需的时间。
 * Latch 等待和类似操作应通过通常的技术立即做出反应。
 */
#define POSTMASTER_POLL_RATE_LIMIT 1024
#endif

/*
 * 由中断处理程序设置的标志，以便在重做循环中稍后处理。
 */
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
static volatile sig_atomic_t promote_signaled = false;

/*
 * 在执行恢复命令时设置的标志，以告知 SIGTERM 信号处理程序
 * 可以安全地执行 proc_exit。
 */
static volatile sig_atomic_t in_restore_command = false;

/*
 * 最近的启动操作开始的时间。
 */
static TimestampTz startup_progress_phase_start_time;

/*
 * 指示用户提到的启动进度间隔是否已经过去。如果发生超时则为 TRUE，否则为 FALSE。
 */
static volatile sig_atomic_t startup_progress_timer_expired = false;

/*
 * 长时间运行的启动操作之间的进度更新间隔。
 */
int			log_startup_progress_interval = 10000;	/* 10 sec */

/* 信号处理程序 */
static void fc_StartupProcTriggerHandler(SIGNAL_ARGS);
static void fc_StartupProcSigHupHandler(SIGNAL_ARGS);

/* 回调 */
static void fc_StartupProcExit(int fc_code, Datum fc_arg);


/* --------------------------------
 *		信号处理程序例程
 * --------------------------------
 */

/* SIGUSR2: 设置标志以完成恢复 */
static void fc_StartupProcTriggerHandler(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	promote_signaled = true;
	WakeupRecovery();

	errno = fc_save_errno;
}

/* SIGHUP: 设置标志以在下一个合适的时机重新读取配置文件 */
static void fc_StartupProcSigHupHandler(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	got_SIGHUP = true;
	WakeupRecovery();

	errno = fc_save_errno;
}

/* SIGTERM: 设置标志以中止重做并退出 */
static void fc_StartupProcShutdownHandler(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	if (in_restore_command)
	{
		/*
		 * 如果我们在一个子进程中（例如，由 system() 在
		 * RestoreArchivedFile() 中分叉），我们不想调用任何退出回调。
		 * 父进程会处理这件事。
		 */
		if (MyProcPid == (int) getpid())
			proc_exit(1);
		else
		{
			write_stderr_signal_safe("StartupProcShutdownHandler() called in child process\n");
			_exit(1);
		}
	}
	else
		shutdown_requested = true;
	WakeupRecovery();

	errno = fc_save_errno;
}

/*
 * 重新读取配置文件。
 *
 * 如果其中一个关键的 walreceiver 选项已更改，标记 xlog.c
 * 以重新启动它。
 */
static void fc_StartupRereadConfig(void)
{
	char	   *fc_conninfo = pstrdup(PrimaryConnInfo);
	char	   *fc_slotname = pstrdup(PrimarySlotName);
	bool		fc_tempSlot = wal_receiver_create_temp_slot;
	bool		fc_conninfoChanged;
	bool		fc_slotnameChanged;
	bool		fc_tempSlotChanged = false;

	ProcessConfigFile(PGC_SIGHUP);

	fc_conninfoChanged = strcmp(fc_conninfo, PrimaryConnInfo) != 0;
	fc_slotnameChanged = strcmp(fc_slotname, PrimarySlotName) != 0;

	/*
	 * wal_receiver_create_temp_slot 仅在我们没有配置插槽时使用。
	 * 如果没有影响，我们不需要跟踪此更改。
	 */
	if (!fc_slotnameChanged && strcmp(PrimarySlotName, "") == 0)
		fc_tempSlotChanged = fc_tempSlot != wal_receiver_create_temp_slot;
	pfree(fc_conninfo);
	pfree(fc_slotname);

	if (fc_conninfoChanged || fc_slotnameChanged || fc_tempSlotChanged)
		StartupRequestWalReceiverRestart();
}

/* 处理可能发送到启动过程的各种信号 */
void HandleStartupProcInterrupts(void)
{
#ifdef POSTMASTER_POLL_RATE_LIMIT
	static uint32 postmaster_poll_count = 0;
#endif

	/*
	 * 处理最近接收到的任何请求或信号。
	 */
	if (got_SIGHUP)
	{
		got_SIGHUP = false;
		fc_StartupRereadConfig();
	}

	/*
	 * 检查是否要求我们在未完成恢复的情况下退出。
	 */
	if (shutdown_requested)
		proc_exit(1);

	/*
	 * 在主进程已死亡的情况下进行紧急撤退。这是为了避免手动清理
	 * 所有主进程子进程的必要性。在我们没有信号可以让这变得轻松的系统上
	 * 更少进行这种操作。
	 */
	if (IsUnderPostmaster &&
#ifdef POSTMASTER_POLL_RATE_LIMIT
		postmaster_poll_count++ % POSTMASTER_POLL_RATE_LIMIT == 0 &&
#endif
		!PostmasterIsAlive())
		exit(1);

	/* 处理屏障事件 */
	if (ProcSignalBarrierPending)
		ProcessProcSignalBarrier();

	/* 执行此进程的内存上下文日志记录 */
	if (LogMemoryContextPending)
		ProcessLogMemoryContextInterrupt();
}


/* --------------------------------
 *		信号处理程序例程
 * --------------------------------
 */
static void fc_StartupProcExit(int fc_code, Datum fc_arg)
{
	/* 关闭恢复环境 */
	if (standbyState != STANDBY_DISABLED)
		ShutdownRecoveryTransactionEnvironment();
}


/* ----------------------------------
 *	启动进程主要入口点
 * ----------------------------------
 */
void StartupProcessMain(void)
{
	/* 安排在启动进程退出时进行清理 */
	on_shmem_exit(fc_StartupProcExit, 0);

	/*
	 * 正确接收或忽略主进程可能发送给我们的信号。
	 */
	pqsignal(SIGHUP, fc_StartupProcSigHupHandler); /* 重新加载配置文件 */
	pqsignal(SIGINT, SIG_IGN);	/* 忽略查询取消 */
	pqsignal(SIGTERM, fc_StartupProcShutdownHandler);	/* 请求关闭 */
	/* SIGQUIT 处理程序已经由 InitPostmasterChild 设置 */
	InitializeTimeouts();		/* 建立 SIGALRM 处理程序 */
	pqsignal(SIGPIPE, SIG_IGN);
	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
	pqsignal(SIGUSR2, fc_StartupProcTriggerHandler);

	/*
	 * 重置一些由postmaster接受但这里不接受的信号
	 */
	pqsignal(SIGCHLD, SIG_DFL);

	/*
	 * 注册备用模式下所需的超时
	 */
	RegisterTimeout(STANDBY_DEADLOCK_TIMEOUT, StandbyDeadLockHandler);
	RegisterTimeout(STANDBY_TIMEOUT, StandbyTimeoutHandler);
	RegisterTimeout(STANDBY_LOCK_TIMEOUT, StandbyLockTimeoutHandler);

	/*
	 * 解锁信号（它们在postmaster分叉我们时被阻止）
	 */
	PG_SETMASK(&UnBlockSig);

	/*
	 * 完成我们来这里的目的。
	 */
	StartupXLOG();

	/*
	 * 正常退出。退出码 0 告诉主进程我们成功完成了恢复
	 * 。
	 */
	proc_exit(0);
}

void PreRestoreCommand(void)
{
	/*
	 * 设置 in_restore_command 以告诉信号处理程序我们应该在 SIGTERM 时立即退出。我们知道在这个安全点可以这样做。
	 * 检查我们是否已经收到了信号，以免错过在此之前收到的关闭请求。
	 */
	in_restore_command = true;
	if (shutdown_requested)
		proc_exit(1);
}

void PostRestoreCommand(void)
{
	in_restore_command = false;
}

bool IsPromoteSignaled(void)
{
	return promote_signaled;
}

void ResetPromoteSignaled(void)
{
	promote_signaled = false;
}

/*
 * 设置一个标志，指示是时候记录进度报告了。
 */
void startup_progress_timeout_handler(void)
{
	startup_progress_timer_expired = true;
}

void disable_startup_progress_timeout(void)
{
	/* 功能已禁用。 */
	if (log_startup_progress_interval == 0)
		return;

	disable_timeout(STARTUP_PROGRESS_TIMEOUT, false);
	startup_progress_timer_expired = false;
}

/*
 * 设置当前操作的开始时间戳并启用超时。
 */
void enable_startup_progress_timeout(void)
{
	TimestampTz fc_fin_time;

	/* 功能已禁用。 */
	if (log_startup_progress_interval == 0)
		return;

	startup_progress_phase_start_time = GetCurrentTimestamp();
	fc_fin_time = TimestampTzPlusMilliseconds(startup_progress_phase_start_time,
										   log_startup_progress_interval);
	enable_timeout_every(STARTUP_PROGRESS_TIMEOUT, fc_fin_time,
						 log_startup_progress_interval);
}

/*
 * 一个薄包装，首先禁用然后启用启动进度超时。
 */
void begin_startup_progress_phase(void)
{
	/* 功能已禁用。 */
	if (log_startup_progress_interval == 0)
		return;

	disable_startup_progress_timeout();
	enable_startup_progress_timeout();
}

/*
 * 报告是否发生了启动进度超时。如果发生了，重置计时器标志，将经过的时间设置为输出参数并返回 true，
 * 否则返回 false。
 */
bool has_startup_progress_timeout_expired(long *fc_secs, int *fc_usecs)
{
	long		fc_seconds;
	int			fc_useconds;
	TimestampTz fc_now;

	/* 没有发生超时。 */
	if (!startup_progress_timer_expired)
		return false;

	/* 计算经过的时间。 */
	fc_now = GetCurrentTimestamp();
	TimestampDifference(startup_progress_phase_start_time, fc_now, &fc_seconds, &fc_useconds);

	*fc_secs = fc_seconds;
	*fc_usecs = fc_useconds;
	startup_progress_timer_expired = false;

	return true;
}
